#region File information

/**
 * File: CollisionHull.cs
 * Date: 29-03-2008
 * Auth: Gerard v/d Lei
 * 
 * Desc: Described a CollisionHull which is a collection of ConvexHulls
 *       Methods are designed to be called from CollisionObject
 */

#endregion

using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System;

namespace Gremlin.Collision
{
    /// <summary>
    /// The CollisionHull describes the shape of on object which is used for collision detection
    /// and responce. It is a collection of ConvexHulls.
    /// </summary>
    public class CollisionHull
    {
        #region Attributes
        private ConvexHull[] _convexHulls;
        private BoundingBox _boundingBox;
        private Vector3 _centerOfGravity;
        private float _radius;
        #endregion

        /// <summary>
        /// Constructs new CollisionHull from primitive data
        /// </summary>
        /// <param name="collisionHull">Primitive data to construct hull from</param>
        public CollisionHull(List<Dictionary<string, object>> collisionHull)
        {
            //Construct separate convex hulls
            _convexHulls = new ConvexHull[collisionHull.Count];
            int count = 0;
            foreach (Dictionary<string, object> hull in collisionHull)
                _convexHulls[count++] = new ConvexHull(hull);

            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue), max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            float tRadius = 0;
            //Go through each hull to create total AABB, center of gravity and radius
            foreach (ConvexHull cHull in _convexHulls)
            {
                BoundingBox box = cHull.BoundingBox;
                min.X = Math.Min(min.X, box.Min.X);
                min.Y = Math.Min(min.Y, box.Min.Y);
                min.Z = Math.Min(min.Z, box.Min.Z);
                max.X = Math.Max(max.X, box.Max.X);
                max.Y = Math.Max(max.Y, box.Max.Y);
                max.Z = Math.Max(max.Z, box.Max.Z);
                _centerOfGravity += cHull.CenterOfGravity * cHull.Radius;
                tRadius += cHull.Radius;
            }
            //Construct primitive bounding sphere
            _centerOfGravity /= tRadius;
            _radius = (float)Math.Sqrt(Math.Max((_centerOfGravity - min).LengthSquared(), (_centerOfGravity - max).LengthSquared()));
            //Construct bounding box
            _boundingBox = new BoundingBox(min, max);
        }

        /// <summary>
        /// Constructs a translated lightweight copy of a collision hull.
        /// Used to get a collision hull in global space needed for collision detection.
        /// </summary>
        /// <param name="reference">CollisionHull in local space to transform</param>
        /// <param name="transform">Complete transform matrix</param>
        /// <param name="rotation">Rotation matrix, only used for rotation normals</param>
        public CollisionHull(CollisionHull reference, Matrix transform, Quaternion rotation)
        {
            _convexHulls = new ConvexHull[reference._convexHulls.Length];
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue), max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            float tRadius = 0;
            for (int hullCount = 0; hullCount < reference._convexHulls.Length; ++hullCount)
            {
                //Construct all convex hulls
                _convexHulls[hullCount] = new ConvexHull(reference._convexHulls[hullCount], transform, rotation);
                BoundingBox box = _convexHulls[hullCount].BoundingBox;
                //Find AABB info
                min.X = Math.Min(min.X, box.Min.X);
                min.Y = Math.Min(min.Y, box.Min.Y);
                min.Z = Math.Min(min.Z, box.Min.Z);
                max.X = Math.Max(max.X, box.Max.X);
                max.Y = Math.Max(max.Y, box.Max.Y);
                max.Z = Math.Max(max.Z, box.Max.Z);
                _centerOfGravity += _convexHulls[hullCount].CenterOfGravity * _convexHulls[hullCount].Radius;
                tRadius += _convexHulls[hullCount].Radius;
            }
            //Make primitive bounding sphere
            _centerOfGravity /= tRadius;
            _radius = (float)Math.Sqrt(Math.Max((_centerOfGravity - min).LengthSquared(), (_centerOfGravity - max).LengthSquared()));
            //Construct bounding box
            _boundingBox = new BoundingBox(min, max);
        }

        #region Accessors
        /// <summary>
        /// Gets the bounding box.
        /// </summary>
        public BoundingBox BoundingBox
        {
            get { return _boundingBox; }
        }

        /// <summary>
        /// Gets the number of vertices of the entire hull
        /// </summary>
        public int VertexCount
        {
            get
            {
                int count = 0;
                foreach (ConvexHull hull in _convexHulls)
                    count += hull.VertexCount;
                return count;
            }
        }

        /// <summary>
        /// Gets center of gravity, which is mean of the center of gravity
        /// of the ConvexHulls weighted with their radius
        /// </summary>
        public Vector3 CenterOfGravity
        {
            get { return _centerOfGravity; }
        }

        /// <summary>
        /// Returns radius that is based on center of gravity and AABB
        /// </summary>
        public float Radius
        {
            get { return _radius;}
        }
        #endregion

        /// <summary>
        /// Finds the closest point a ray intersects with this collision object
        /// </summary>
        /// <param name="origin">Origin of ray</param>
        /// <param name="delta">Direction of ray</param>
        /// <param name="point">Point where ray intersects with hull</param>
        /// <returns>True when an intersection was found, false otherwise</returns>
        public bool RayIntersection(Vector3 origin, Vector3 delta, out Vector3 point)
        {
            //Quick check
            if(_boundingBox.Intersects(new Ray(origin, delta))== null)
            {
                point = new Vector3();
                return false;
            }
            float distance = float.MaxValue;
            float temp;
            Vector3 closest = new Vector3();
            //Check each hull
            foreach (ConvexHull hull in _convexHulls)
            {
                if (hull.RayIntersection(origin, delta, out point))
                    if ((temp = Vector3.DistanceSquared(point, origin)) < distance)
                    {
                        //Safe info when it is closest point
                        distance = temp;
                        closest = point;
                    }
            }
            //If nothing found return false, otherwise set closest point and return true
            if (distance == float.MaxValue)
            {
                point = new Vector3();
                return false;
            }else
            {
                point = closest;
                return true;
            }
        }

        /// <summary>
        /// Finds the closest point a ray intersects with this collision object
        /// </summary>
        /// <param name="origin">Origin of ray</param>
        /// <param name="delta">Direction of ray</param>
        /// <param name="point">Point where ray intersects with hull</param>
        /// <returns>True when an intersection was found, false otherwise</returns>
        public bool RayIntersection(Vector3 origin, Vector3 delta, out Vector3 point, out TPlane plane)
        {
            //First to quick check against bounding box
            if (_boundingBox.Intersects(new Ray(origin, delta))==null)
            {
                point = new Vector3();
                plane = new TPlane();
                return false;
            }
            float distance = float.MaxValue;
            float temp;
            Vector3 closest = new Vector3();
            TPlane outPlane = new TPlane();
            //Check each hull
            foreach (ConvexHull hull in _convexHulls)
            {
                if (hull.RayIntersection(origin, delta, out point, out plane))
                    if ((temp = Vector3.DistanceSquared(point, origin)) < distance)
                    {
                        //If closest save point and plane
                        distance = temp;
                        closest = point;
                        outPlane = plane;
                    }
            }

            //If nothing found return false, otherwise set point and plane and return true
            plane = outPlane;
            if (distance == float.MaxValue)
            {
                point = new Vector3();
                return false;
            }
            else
            {
                point = closest;
                return true;
            }
        }

        /// <summary>
        /// Finds if a ray intersects with this collision object
        /// </summary>
        /// <param name="origin">Origin of ray</param>
        /// <param name="delta">Direction of ray</param>
        /// <param name="point">Point where ray intersects with hull</param>
        /// <returns>True when an intersection was found, false otherwise</returns>
        public bool RayIntersection(Vector3 origin, Vector3 delta)
        {
            //First to quick check against bounding box
            if (_boundingBox.Intersects(new Ray(origin, delta))==null)
                return false;
            //Check each hull
            Vector3 point;
            //Only interested in first intersection, find only the first, return true if found
            foreach (ConvexHull hull in _convexHulls)
            {
                if (hull.RayIntersection(origin, delta, out point))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Checks if two collision hull intersect.
        /// </summary>
        /// <param name="ch0">First collision hull</param>
        /// <param name="ch1">Second collision hull</param>
        /// <param name="centerPoint">A single point which is center of collision</param>
        /// <returns>True when intersection occoured, false otherwise</returns>
        public static bool CollisionHullIntersection(CollisionHull ch0, CollisionHull ch1, out Vector3 centerPoint)
        {
            centerPoint = new Vector3(0, 0, 0);
            int collision = 0;
            Vector3 center = new Vector3(0,0,0);
            //Two collision hulls can only intersect when AABB intersect
            if (ch0._boundingBox.Intersects(ch1._boundingBox))
            {
                //A collision hull can only intersect when convexhulls intersect
                foreach (ConvexHull hull0 in ch0._convexHulls)
                    foreach (ConvexHull hull1 in ch1._convexHulls)
                    {
                        //Two hulls can only collide when AABB intesect
                        if (hull0.BoundingBox.Intersects(hull1.BoundingBox))
                        {
                            List<Vector3> points;
                            //Do the actual convex hull intesection algorithm
                            if(hull0.ConvexHullIntersection(hull1, out points))
                                //If their was an intersection we are interested in where this happened
                                foreach (Vector3 p in points)
                                {
                                    ++collision;
                                    center += p;
                                }
                        }
                    }
            }
            //Ultimatly if we did collide, handle this
            if (collision>0)
            {
                centerPoint = center / collision;
                return true;
            }
            return false;
        }
    }
}
